using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Infinity.Common;
using Infinity.Common.Extensions;

namespace Infinity.Statements.Utilities
{
	/// <summary>
	/// Manager class for types
	/// </summary>
	public class TypeManager : ITypeManager
	{
		/// <summary>
		/// It contains the loaded assemblies.
		/// </summary>
		protected List<Assembly> m_Assemblies { get; set; }

		/// <summary>
		/// It contains name of types and the associated assemblies.
		/// </summary>
		protected Dictionary<string, List<Type>> m_NameTypeDictionary { get; set; }

		protected Dictionary<Type, List<string>> m_TypeNameDictionary { get; set; }

		/// <summary>
		/// Default constructor
		/// </summary>
		public TypeManager()
		{
			m_NameTypeDictionary = new Dictionary<string, List<Type>>();
			m_Assemblies = new List<Assembly>();
			m_TypeNameDictionary = new Dictionary<Type, List<string>>();
		}

		/// <summary>
		/// If types from Assembly
		/// </summary>
		public void Add(Assembly assembly)
		{
			Ensure.NotNull(assembly);

			if (m_Assemblies.Contains(assembly))
				return;

			m_Assemblies.Add(assembly);

			foreach (var type in assembly.GetTypes())
				Add(type);
		}

		/// <summary>
		/// It adds a new assembly
		/// </summary>
		/// <param name="assembly">Assembly's name</param>
		public void Add(string assembly)
		{
			Ensure.NotBlank(assembly);

			Add(Assembly.Load(assembly));
		}

		public void Add(string name, Type type)
		{
			Ensure.NotBlank(name);
			Ensure.NotNull(type);

			var names = name.Split(new[]
				                       {
					                       "."
				                       }, StringSplitOptions.RemoveEmptyEntries);

			m_TypeNameDictionary[type] = new List<string>();

			for (int i = 0; i < names.Length; i++)
			{
				string typeName = string.Join(".", names, i, names.Length - i);

				List<Type> result;
				if (!m_NameTypeDictionary.TryGetValue(typeName, out result))
					m_NameTypeDictionary.Add(typeName, new List<Type>
						                                   {
							                                   type
						                                   });
				else
					m_NameTypeDictionary[typeName].Add(type);
			}
		}

		public void Add<T>(string name)
		{
			Add(name, typeof(T));
		}

		/// <summary>
		/// It adds a new type
		/// </summary>
		public void Add(Type type)
		{
			Ensure.NotNull(type);
			Ensure.NotNull(m_TypeNameDictionary);
			Ensure.NotNull(m_NameTypeDictionary);

			if (m_TypeNameDictionary.ContainsKey(type))
				return;

			if (type.FullName.IsNotNull())
				Add(type.FullName, type);
		}

		/// <summary>
		/// It removes type from TypeManager
		/// </summary>
		public bool Remove(Type type)
		{
			Ensure.NotNull(type);
			Ensure.NotNull(m_TypeNameDictionary);
			Ensure.NotNull(m_NameTypeDictionary);

			List<string> names;
			if (m_TypeNameDictionary.TryGetValue(type, out names))
			{
				m_TypeNameDictionary.Remove(type);
				foreach (var key in names)
					m_NameTypeDictionary.Remove(Ensure.NotBlank(key));
				return true;
			}
			return false;
		}

		/// <summary>
		/// It unload the given assembly from TypeManager
		/// </summary>
		public bool Remove(Assembly assembly)
		{
			return m_Assemblies.Remove(assembly);
		}

		/// <summary>
		/// It resolves the given type name and returns
		/// </summary>
		public Type ResolveType(string typeName)
		{
			Ensure.NotBlank(typeName);
			Ensure.NotNull(m_NameTypeDictionary);

			typeName = typeName.Trim();

			List<Type> result;
			if (m_NameTypeDictionary.TryGetValue(typeName, out result))
				return result.First();

			return null;
		}

		/// <summary>
		/// It resolves type by name. If there is not in any assembly, then it returns false. Otherwise it returns true.
		/// </summary>
		public bool TryResolveType(string typeName, out Type type)
		{
			type = ResolveType(typeName);
			return type != null;
		}

		/// <summary>
		/// Get types
		/// </summary>
		public Type[] GetTypes()
		{
			return m_TypeNameDictionary.Keys.ToArray();
		}

		/// <summary>
		/// Get types by name
		/// </summary>
		public IEnumerable<Type> GetTypes(string name)
		{
			Ensure.NotBlank(name);
			name = name.Trim();

			List<Type> types;
			if (m_NameTypeDictionary.TryGetValue(name, out types))
				return types.ToArray();

			throw new Exception(string.Format("There is no loaded type with this name: {0}!", name));
		}
	}
}